//---------------------------------------------------------------------------

#include <vcl.h>
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <sysutils.hpp>
#include <system.hpp>
#include <DateUtils.hpp>

#pragma hdrstop

#include "string.h"
#include "Time.h"
#include "SerialPortThread.h"
#pragma package(smart_init)
//---------------------------------------------------------------------------

//   Important: Methods and properties of objects in VCL can only be
//   used in a method called using Synchronize, for example:
//
//      Synchronize(UpdateCaption);
//
//   where UpdateCaption could look like:
//
//      void __fastcall SerialPort::UpdateCaption()
//      {
//        Form1->Caption = "Updated in a thread";
//      }
//---------------------------------------------------------------------------


__fastcall SerialPort::SerialPort(bool CreateSuspended)
    : TThread(CreateSuspended)
{
}


//---------------------------------------------------------------------------
void SerialPort::SetName()
{
    THREADNAME_INFO info;
    info.dwType = 0x1000;
    info.szName = "SerialPort";
    info.dwThreadID = -1;
    info.dwFlags = 0;

    __try
    {
        RaiseException( 0x406D1388, 0, sizeof(info)/sizeof(DWORD),(DWORD*)&info );
    }
    __except (EXCEPTION_CONTINUE_EXECUTION)
    {
    }
}

//---------------------------------------------------------------------------
void __fastcall SerialPort::Execute()
{
    SetName();
    //---- Place thread code here ----
    ReceiveSet = false;
    PortNumber = 0;
    BaudRate = 0;
    Parity = PAR_NONE;
    RxSize = 0;
    SerialState = STATE_CLOSED;
    ComPortHandle = NULL;
    ReceiveFunc = NULL;
    Error = SERIAL_ERROR_NONE;
    NewCommTimeouts.ReadIntervalTimeout = MAXDWORD;
    NewCommTimeouts.ReadTotalTimeoutMultiplier = 0;
    NewCommTimeouts.ReadTotalTimeoutConstant = 0;
    NewCommTimeouts.WriteTotalTimeoutMultiplier = 10;
    NewCommTimeouts.WriteTotalTimeoutConstant = 1000;
    PortNumber = 34;

    SubmitStatus = NULL;
    ResetBuffer();

    while(1)
    {
		CrankStateMachine();
		TransferStateMachine();
        Sleep(1);
    }

}
//---------------------------------------------------------------------------

void SerialPort::CrankStateMachine()
{
    switch(SerialState)
    {
    case STATE_ATTEMPT_OPEN:
        if(PortOpened())
        {
            SerialState = STATE_OPEN;
        }

        break;

    case STATE_CLOSED:
        break;

    case STATE_OPEN:
        if(ReadFile(ComPortHandle, &RxBuffer, INBUFFER_SIZE, &RxSize, NULL))
        {
            if(RxSize > 0)
            {
                Synchronize(TransferRxData);
            }
        }
        else
        {
            ClosePort();
        }
        break;
    }
}

void SerialPort::ClosePort(void)
{
    LastError = GetLastError ();
    PurgeComm(ComPortHandle, PURGE_RXABORT);
    SetCommTimeouts(ComPortHandle, &OldCommTimeouts);
    CloseHandle(ComPortHandle);
    SerialState = STATE_CLOSED;
}

bool SerialPort::PortOpened(void)
{
    char Tarray[20];
    bool RetVal = false;
    if(BaudRate == 0)
    {
        Error = SERIAL_ERROR_BAUD_NOT_SET;
        return(false);
    }
    if(PortNumber == 0)
    {
        Error = SERIAL_ERROR_PORT_NOT_SET;
        return(false);
    }
    if(ReceiveFunc == NULL)
	{
		Error = SERIAL_ERROR_RX_CALLBACK_NOT_SET;
		return(false);
	}
    if(SubmitStatus == NULL)
    {
        Error = SERIAL_ERROR_RX_CALLBACK_NOT_SET;
        return(false);
    }
    sprintf(Tarray,"COM%i",PortNumber);
    ComPortHandle = CreateFile(Tarray,GENERIC_READ | GENERIC_WRITE,0,0,OPEN_EXISTING,0,0);
    if(ComPortHandle == INVALID_HANDLE_VALUE)
    {
        Error = SERIAL_ERROR_COULD_NOT_OPEN_PORT;
        ClosePort();
    }
    else
    {
        GetCommTimeouts(ComPortHandle,&OldCommTimeouts);
        if (!SetCommTimeouts (ComPortHandle, &NewCommTimeouts))
        {
            Error = SERIAL_ERROR_COULD_NOT_SET_TIME_OUTS;
            LastError = GetLastError ();
            ClosePort();
        }
        else
        {
            dcbCommPort.DCBlength = sizeof(DCB);
            GetCommState(ComPortHandle, &dcbCommPort);
            sprintf(Tarray,"%i,%c,8,1",BaudRate,GetParityChar());
            BuildCommDCB(Tarray, &dcbCommPort);
            SetCommState(ComPortHandle, &dcbCommPort);
            Error = SERIAL_ERROR_NONE;
            SerialState = STATE_OPEN;
			RetVal = true;
        }
    }
	return(RetVal);
}

void __fastcall SerialPort::TransferRxData()
{
	if ( STATE_RECEIVE == m_TransferState )
	{
		for(S32 i = 0; i < RxSize; i++)
		{
			m_pRxFrameBuff[m_RxFrameBuffCnt++] = RxBuffer[i];
		}
		SetUserTimer(&m_Timeout, 20);
	}
	else
	{
		ReceiveFunc(RxSize,&RxBuffer[0]);
    }
    RxSize = 0;
}

char SerialPort::GetParityChar(void)
{
    char RetVal;
    switch(Parity)
    {
    default:
    case PAR_NONE:
        RetVal = 'N';
        break;

    case PAR_ODD:
        RetVal = 'O';
        break;

    case PAR_EVEN:
        RetVal = 'E';
        break;
    }
    return(RetVal);
}


void SerialPort::ResetBuffer(void)
{
    m_FreeCmd.Init(m_FreeCmdStore, PENDING_CMD_MAX);
    m_PendingCmd.Init(m_PendingCmdStore, PENDING_CMD_MAX);
    for (U8 i = 0; i < PENDING_CMD_MAX; i++)
    {
        m_FreeCmd.Push(i);
        m_CmdData[i].m_Data.CmdDone = true;
    }
}

void SerialPort::ClearRxBuffer(void)
{
	m_RxFrameBuffCnt = 0;
	SetUserTimer(&m_Timeout, 100);
}


void SerialPort::TransferStateMachine(void)
{
	switch (m_TransferState)
    {
	default:
		{
			m_TransferState = STATE_IDLE;
		}	// don't break;
	case STATE_IDLE:
        {
            if (true == m_PendingCmd.Pop(m_ProcessingCmdHandle))
            {
				cCommCommand* pCmd = &m_CmdData[m_ProcessingCmdHandle];
                Transmit(pCmd->m_Data.Length, pCmd->m_Data.Buff);
				m_TransferState = STATE_RECEIVE;
				ClearRxBuffer();
            }
        } break;

	case STATE_RECEIVE:
        {
			if (true == UserTimerHasExpired(&m_Timeout))
			{
				cCommCommand* pCmd = &m_CmdData[m_ProcessingCmdHandle];

				if (0 < m_RxFrameBuffCnt)
				{
					if (COMMAND_BUFF_LENGTH_MAX < m_RxFrameBuffCnt)
					{
						pCmd->m_Data.Status = cCommCommand::RECEIVE_OVERFLOW;
						pCmd->CopyData(m_pRxFrameBuff, COMMAND_BUFF_LENGTH_MAX);
					}
					else
					{
						pCmd->m_Data.Status = cCommCommand::RECEIVE_DONE;
						pCmd->CopyData(m_pRxFrameBuff, m_RxFrameBuffCnt);
					}
				}
				else
				{
					pCmd->m_Data.Status = cCommCommand::RECEIVE_TIMEOUT;
					pCmd->m_Data.Length = 0;
				}
				pCmd->m_Data.CmdDone = true;
				SubmitStatus(pCmd);
				m_RxFrameBuffCnt = 0;

				m_FreeCmd.Push(m_ProcessingCmdHandle);

				m_TransferState = STATE_IDLE;
			}
        } break;
    }
}

/******************/
/* User Interface */
/******************/

void SerialPort::Open(void)
{
    if(SerialState == STATE_CLOSED)
    {
        SerialState = STATE_ATTEMPT_OPEN;
    }
}

void SerialPort::Close(void)
{
    ClosePort();
}

void SerialPort::Transmit(int NumBytes, char *OutBuffer)
{
    DWORD NumBytesWritten;
    if(SerialState == STATE_OPEN)
    {
        if(!WriteFile (ComPortHandle, OutBuffer, NumBytes, &NumBytesWritten, NULL))
        {
            LastError = GetLastError ();
        }
    }
}

void SerialPort::SetupRxCallback(void (*RxCallBackFuncPtr)(int NumBytes, unsigned char *Buffer))
{
    ReceiveFunc = RxCallBackFuncPtr;
    ReceiveSet = true;
}

bool SerialPort::Connected(void)
{
    if(SerialState == STATE_OPEN)
    {
        return(true);
    }
    return(false);
}

bool SerialPort::SubmitCmd(U8 iOp, U8* iBuff, U16 iLen)
{
    U8 cmdHandle;

	if (false == Connected()) return (false);

    if (true == m_FreeCmd.Pop(cmdHandle))
    {
		cCommCommand* pCmd = &m_CmdData[cmdHandle];

        if ( false == pCmd->CopyData(iBuff, iLen) ) return (false);
		pCmd->m_Data.Operation = iOp;
        pCmd->m_Data.CmdDone = false;
		pCmd->m_Data.Status  = cCommCommand::SEND_PENDING;
        if ( true == m_PendingCmd.Push(cmdHandle) )
        {
            return (true);
        }

        pCmd->m_Data.CmdDone = true;
		m_FreeCmd.Push(cmdHandle);
    }
    return (false);
}

void SerialPort::SetupSubmitStatusCallback(void (*SubmitStatusFuncPtr)(cCommCommand* pCmd))
{
    SubmitStatus = SubmitStatusFuncPtr;
}

/* End of file */
